package org.wadael.waverobotfrmwrk.advanced;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.wadael.waverobotfrmwrk.WadRobot;
import org.wadael.waverobotfrmwrk.utils.BlipUtils;
import org.wadael.waverobotfrmwrk.utils.CommandUtils;

import com.google.wave.api.Blip;
import com.google.wave.api.event.Event;
import com.google.wave.api.impl.EventMessageBundle;

@SuppressWarnings("serial")
public abstract class WithWorkersRobot  extends WadRobot implements AdvancedRobot, WithDelimitedCommandsRobot 
{

	protected ArrayList<RobotWorker> workers = null; // passer en hashmap ?

	public WithWorkersRobot() {
		super();
		workers = new ArrayList<RobotWorker>();
	}

	
	/**
	 * Add a worker to this robot.
	 * 
	 * @param rw a RobotWorker, not null
	 */
	public void addRobotWorker(RobotWorker rw) {
		if(rw!=null) workers.add(rw);
		/* TODO check no worker with same instruction 
		 * is not already present.
		 * Although unlikely, it is still possible in the case of a robotworker 
		 * with a lot of reused workers.   
		 */ 
	}

	/** 
	 * Is the user who is the author of the processed blip an authorized user ? 
	 */
	public boolean isUserAuthorized(Event event, WithWorkersRobot robot){
		return BlipUtils.isUserAnAuthorizedUser(event, this);
	}
	
	/**
	 * Do not override unless you do not want to use this framework.
	 */
	@Override
	protected void reactToSubmittedBlip(EventMessageBundle bundle, Blip blip,
			Event event) {
		/* if no workers : return to preserve CPU usage. */
		if (workers.size() == 0)
			return;

		// if(!BlipUtils.isUserAnAuthorizedUser(event, this)) return;  /* check user's rights to execute */
		
		if(!isUserAuthorized(event, this)){ 
			onUnauthorizedUser(event);
			return;
		}
		/* TODO check / lifecycle if users are not programatically set but added/deleted at runtime 
		*/
	
		String blipText = BlipUtils.getEventBlipText(event);  
		
		/* make it start by a help/usage request verification ? */
		if ( blipText.startsWith( getHelpInstruction()) || blipText.startsWith("\n" + getHelpInstruction()) ){
			showUsage(bundle, blip);
			return;
		}

		boolean ret = true;

		List<Command> commandsFound = CommandUtils.parseCommands(blipText, this);

		if (commandsFound == null || commandsFound.size() == 0) {
			/*
			 * in that case, we run the workers with instruction == null anyway
			 * and stop execution of robot 
			 * TODO make this behaviour a parameter + a subclass?
			 */
			Iterator<RobotWorker> iterWorkers = workers.iterator(); 
			/* iterate the workers */
			while (iterWorkers.hasNext()) {
				RobotWorker rw = iterWorkers.next();
				if (rw.getInstruction() == null)
					rw.doWork(bundle, blip, event, null);
			}
			return;
		}

		// iterate the commands
		Iterator<Command> iterCommands = commandsFound.iterator();

		while (iterCommands.hasNext()) {
			Command c = iterCommands.next();
			boolean workerFoundForCommand = false;
			// then iterate the workers as
			Iterator<RobotWorker> iterWorkers = workers.iterator(); /* iterate  the  workers */
			while (ret && iterWorkers.hasNext()) {
				RobotWorker rw = iterWorkers.next();
				if (rw.getInstruction() != null
						&& (rw.getInstruction().equals(c.getInstruction())) || rw.getInstruction().equals("\n"+c.getInstruction())){
					// TODO drop that || 2nd condition as soon as the \n is not mandatory anymore.
					workerFoundForCommand = true ;
					ret = rw.doWork(bundle, blip, event, c.getParameters());
					// ret = rw.doWork(bundle, blip, event, c );
					// if(c.isToCleanAfterUse()){
					
					String toRemove =  getStartCommandDelimiter() + c.getAcronym()+  getAcronymAndCommandSeparator() + c.getInstruction() 
					+ ( (c.getParameters()==null)?"":c.getParameters() ) +  getEndCommandDelimiter()    ;
					
					if( rw.areCommandsToClean() ){
						BlipUtils.removeCommandFromBlip(c, blip, this);
					}
					else{
						String tmp = rw.getReplacingValue(c, bundle, blip, event);
						if(tmp != null){
							BlipUtils.replaceCommandFromBlip(c, blip, this, tmp);
						}
					}
					break;
				}
			}
			if(!workerFoundForCommand)
				reactToUnknownCommand(c, bundle, blip, event);
		}
	}
	
	/**
	 * Method called when an unauthorized user tries to uses the robot 
	 * @param event
	 */
	public void onUnauthorizedUser(Event event) {
		// TODO Auto-generated method stub
		
	}

	/** 
	 * Should (all) commands be 'cleaned' by default.
	 * returns false by default 
	 */
	public boolean areCommandsToClean() {
		return false;
	}
	
	/**
	 * Default implementation for the behaviour 
	 * a robot must have if a command asked by the user does not exist 
	 * (no corresponding worker or syntax error)
	 */
	protected void reactToUnknownCommand(Command unknownCommand, EventMessageBundle bundle,Blip blip, Event event) {
		blip.append("\nCommand '"+ unknownCommand.getInstruction() +"' can not be run : No matching worker.");
	}
	
	/**
	 * Uses getUsage() method of each worker to return value 
	 * to add it into a new blip.
	 * 
	 * @param bundle
	 * @param blip
	 */
	protected void showUsage(EventMessageBundle bundle, Blip blip) {
		if (getUsage() != null) {
			Blip blipUsage = blip.reply();
			BlipUtils.replaceBlipContent( blipUsage, getUsage() );
		} else
			log("Unable to answer to users help requests when getUsage returns null");
	}

	/**
	 * Returns a String that shows what can be performed by the robot (or better
	 * its workers). It lists the instructions in sequential, processing order.
	 * Each instruction is followed by its eventual description.
	 */
	protected String getUsage() {
		if (workers == null || workers.size() == 0) {
			return null; // 
		} else {
			StringBuffer usage = new StringBuffer();
			usage.append("Usage for robot " + getDisplayName() + ":");
			usage.append("\n");
			usage.append("\n");
			usage.append("Usage is of form   " + getStartCommandDelimiter()+getRobotIdentifier()+getAcronymAndCommandSeparator() +"instruction optionnal_parameters"+getEndCommandDelimiter() );
//			usage.append("\n");
//			usage.append("With possible optional space and parameters before final "+getEndCommandDelimiter() );
			usage.append("\n");
			usage.append("\n");
			usage.append("List of available instructions is :\n");
			Iterator<RobotWorker> iterWorkers = workers.iterator();

			while (iterWorkers.hasNext()) {
				RobotWorker rw = iterWorkers.next();
				if (rw.getInstruction() != null) {
					// i.e. not an hidden worker
					usage.append(rw.getInstruction());
					if (rw.getDescription() != null) {
						usage.append(" : ");
						usage.append(rw.getDescription());
					}
					usage.append("\n");
				}
			}
			usage.append("\n");
			usage.append("Instructions' case matters.");
			usage.append("\n");
			
			return usage.toString();
		}
	}

	/**
	 * Returns the part that is common to all commands. 
	 * Must be unique for each robot in a wave. 
	 * Must be short so that users do not get bored typing it.
	 * 
	 * @return
	 */
	public abstract String getRobotIdentifier();

	/**
	 * Returns a character that separates the acronym of the robot from the
	 * command. Default is :
	 */
	public String getAcronymAndCommandSeparator() {
		return ":";
	};

	/**
	 * Returns the character that marks the beginning of the acronym Default is
	 * { as for our example usage {cwr:search foo}
	 */
	public String getStartCommandDelimiter() {
		return "{";
	};

	/**
	 * Returns the character that marks the end of the command Default is } as
	 * for our example usage {cwr:search foo}
	 */
	public String getEndCommandDelimiter() {
		return "}";
	};

	/**
	 * Returns the string to recognize as a request for help from a waver
	 * Default is "help". So that it can take {cwr:help} to get help (this is
	 * going to be improved). 
	 * This method exist so that you can localize it if you create a non-english robot.
	 * Default is /help   (slash help)
	 */
	public String getHelpInstruction() {
		return "/help";
	}
}

/* TODO
 * il faut gérer l'ajout de workers ayant mm clé (runtimeException) il faut
 * passer le parametre de la commande au robot il faut vérifier que lorsqu'un
 * character est attendu, la longueur vaut 1
 * 
 * il faut faire une liste de mots réservés.
 */